package Algorithms;

import NeuronNetworkLibrary.Network;
import java.util.ArrayList;
import java.util.Random;

/**
 *
 * @author Zbyszko
 */
public class NeuralCrossValidation {

    private int KFoldParam;
    ArrayList<double[][]> trainingSetList = new ArrayList<>();
    ArrayList<double[][]> outputList = new ArrayList<>();
    private double error;
    private Network network;
    double[][] trainingData;
    double[][] outputData;

    public NeuralCrossValidation(Network n) {
        this.network = n;
        this.KFoldParam = this.chooseKFoldParam();
        System.out.println("k-fold param: " + this.KFoldParam);

        this.trainingData = this.network.getTrainingSet().clone();
        this.outputData = this.network.getDesiredOutputs().clone();
        this.shuffleSets();

        this.splitSets();
    }

    private int chooseKFoldParam() {
        int trainingSize = this.network.getTrainingSet().length;

        if (trainingSize < 50) {
            return 5;
        } else {
            return 10;
        }
    }

    private void shuffleSets() {
        Random randomValue = new Random();  // Random number generator

        //--- Shuffle by exchanging each element randomly
        for (int i = 0; i < this.trainingData.length; i++) {
            int randomPosition = randomValue.nextInt(trainingData.length);
            double[] temp1 = this.trainingData[i];
            double[] temp2 = this.outputData[i];
            this.trainingData[i] = this.trainingData[randomPosition];
            this.outputData[i] = this.outputData[randomPosition];
            this.trainingData[randomPosition] = temp1;
            this.outputData[randomPosition] = temp2;
        }

    }

    private void splitSets() {

        int widthLength = this.trainingData[0].length;
        int outputWidthLength = this.outputData[0].length;

        double[][] temp = new double[KFoldParam][widthLength];
        double[][] outputTemp = new double[KFoldParam][outputWidthLength];
        int j = 0;

        for (int i = 0; i < this.trainingData.length; i++) {
            temp[j] = this.trainingData[i];
            outputTemp[j] = this.outputData[i];
            j++;
            if (j == this.KFoldParam) {
                j = 0;
                this.trainingSetList.add(temp.clone());
                this.outputList.add(outputTemp.clone());
            }
        }
    }

    public void performCrossValidation() {
        this.error = 0;

        int i = 0;
        while (i < this.trainingSetList.size()) {
            
            for (int k = 0; k < this.trainingSetList.size(); k++) {
                if (i != k) {
                    this.network.setTrainingSet(this.trainingSetList.get(k));
                    this.network.setDesiredOutputs(this.outputList.get(k));
                    this.network.updateNumberOfPatterns();
                    for (int m = 0; m < this.network.getNumberOfPatterns(); m++) {
                        this.network.calculateNetwork(m);
                        BackPropagation.calculateBackPropagation(this.network);
                    }
                }
            }
            
            this.network.setTrainingSet(this.trainingSetList.get(i));
            this.network.setDesiredOutputs(this.outputList.get(i));
            this.network.updateNumberOfPatterns();
            for (int n = 0; n < this.network.getNumberOfPatterns(); n++) {
                this.network.calculateNetwork(n);
            }
            
            this.error += this.network.calculateRMSE();
            i++;
        }
    }

    public int getKFoldParam() {
        return KFoldParam;
    }

    public double getError() {
        return error;
    }

    public Network getNetwork() {
        return network;
    }

    public void setNetwork(Network network) {
        this.network = network;
    }   
}
